package com.beantools.filters;

import com.beantools.entity.enums.NumberTypeEnum;
import com.beantools.execption.InformationException;

import java.io.Serializable;
import java.math.BigDecimal;

import static com.beantools.constants.MainASCCode.CHAR_SPACE;
import static com.beantools.constants.SingleChar.BLANK_OP;
import static com.beantools.constants.SingleChar.NULL_OP;
import static com.beantools.filters.NumberFilters.CharReplacedEnum.REPLACED_STAR;
import static com.beantools.util.ObjectUtil.isNull;
import static com.beantools.util.StringUtil.isEmpty;
import static com.beantools.util.StringUtil.isNotEmpty;
import static java.math.BigDecimal.*;

/**
 * 这是过滤与字符数据相关的过滤器，比如对于手机号<kbd>12345678901</kbd>
 * <pre>
 *     <code>
 *          String phone = "12345678901";
 *          phone = toMask(phone, "*", 4,8);
 *          System.out.println(phone);
 *          输出结果为：
 *              123 **** 8901
 *      </code>
 * </pre>
 *
 * @author baoya
 * @version 1.0
 * @see BuilderFilters
 * @since 1.1.0
 */

public class NumberFilters extends AbstractFilters implements Serializable {

    private static final long serialVersionUID = -3810885224673553706L;

    /**
     * 创建无参构造器
     */
    public NumberFilters() {
    }

    /**
     * 这是将常用的手机号、身份证号、序列号、银行卡号，转化为掩码的方法，格式化输出掩码
     * <p>
     * 比如对于手机号<kbd>12345678901</kbd>，调用者调用该方法
     * <pre>
     *     <code>
     *          String phone = "12345678901";
     *          phone = toMask(phone, "*", 4,8);
     *          System.out.println(phone);
     *          输出结果为：
     *              123 **** 8901
     *      </code>
     * </pre>
     * <p>
     * 比如对于银行卡号<kbd>1234567890011234567</kbd>，调用者调用该方法
     * <pre>
     *     <code>
     *          String bankCardNo = "1234567890011234567";
     *          bankCardNo = toMask(bankCardNo, "*", 5,17);
     *          System.out.println(bankCardNo);
     *          输出结果为：
     *              1234 **** **** **** 567
     *      </code>
     * </pre>
     *
     * @param charSequence 文本内容
     * @param flag         替换符号
     * @param format       按照什么格式输出,比如 手机号码 *** **** ****
     * @param numberType   这是枚举类型，即常用的数据类型
     */
    public String toFormatCommonMask(String charSequence, String format, CharReplacedEnum flag, NumberTypeEnum numberType) {
        switch (numberType) {
            case NUMBER_PHONE:
                if (isEmpty(format)) {
                    format = "*** **** ****";
                }
                return toFormatMask(charSequence, format, flag, ROUND_HALF_UP, charSequence.length() - ROUND_FLOOR);
            case NUMBER_BANK_CARD:
                if (isEmpty(format)) {
                    format = "**** **** **** **** ***";
                }
                return toFormatMask(charSequence, format, flag, ROUND_HALF_DOWN, charSequence.length() - ROUND_CEILING);
            case NUMBDER_ID:
                if (isEmpty(format)) {
                    format = "**** **** **** **** **";
                }
                return toFormatMask(charSequence, format, flag, ROUND_HALF_DOWN, charSequence.length() - ROUND_DOWN);
            default:
                return "";
        }
    }

    /**
     * 这是将手机号、身份证号、序列号、银行卡号等，转化为掩码的方法，格式化输出掩码
     * <p>
     * 比如对于手机号<kbd>12345678901</kbd>，调用者调用该方法
     * <pre>
     *     <code>
     *          String phone = "12345678901";
     *          phone = toMask(phone, "*", 4,8);
     *          System.out.println(phone);
     *          输出结果为：
     *              123 **** 8901
     *      </code>
     * </pre>
     * <p>
     * 比如对于银行卡号<kbd>1234567890011234567</kbd>，调用者调用该方法
     * <pre>
     *     <code>
     *          String bankCardNo = "1234567890011234567";
     *          bankCardNo = toMask(bankCardNo, "*", 5,17);
     *          System.out.println(bankCardNo);
     *          输出结果为：
     *              1234 **** **** **** 567
     *      </code>
     * </pre>
     *
     * @param charSequence 文本内容
     * @param flag         替换符号
     * @param start        文本内容的开始位置
     * @param end          文本内容的结束位置，但不包含该位置
     * @param format       按照什么格式输出,比如 手机号码 *** **** ****
     */
    public String toFormatMask(String charSequence, String format, CharReplacedEnum flag, int start, int end) {
        format = getFormat(charSequence, format);
        String maskSequence = toMask(charSequence, flag, start, end);
        StringBuilder sb = new StringBuilder();
//        这是掩码的下标
        int maskIndex = 0;
        for (int i = 0; i < format.length(); i++) {
            if (format.charAt(i) == CHAR_SPACE) {
                sb.append(BLANK_OP);
                continue;
            }
            sb.append(maskSequence.charAt(maskIndex++));
        }
        return sb.toString();
    }

    /**
     * 这是将手机号、身份证号、序列号、银行卡号等，转化为掩码的方法，采用左闭右开的模式。
     * <p>
     * 比如对于手机号<kbd>12345678901</kbd>，调用者调用该方法
     * <pre>
     *     <code>
     *          String phone = "12345678901";
     *          phone = toMask(phone, "*", 4,8);
     *          System.out.println(phone);
     *          输出结果为：
     *              123****8901
     *      </code>
     * </pre>
     * <p>
     * 比如对于银行卡号<kbd>1234567890011234567</kbd>，调用者调用该方法
     * <pre>
     *     <code>
     *          String bankCardNo = "1234567890011234567";
     *          bankCardNo = toMask(bankCardNo, "*", 5,17);
     *          System.out.println(bankCardNo);
     *          输出结果为：
     *              1234************567
     *      </code>
     * </pre>
     *
     * @param charSequence 文本内容
     * @param flag         替换符号
     * @param start        文本内容的开始位置
     * @param end          文本内容的结束位置，但不包含该位置
     */
    public String toMask(String charSequence, CharReplacedEnum flag, int start, int end) {
        flag = getFlag(charSequence, flag);
        charSequence = charSequence.replace(BLANK_OP, NULL_OP);
        StringBuilder sb = new StringBuilder();
        validStartEnd(charSequence, start, end);
        for (int i = 0; i < charSequence.length(); i++) {
            if (i < end - 1 && i >= start - 1) {
                sb.append(flag.getIdentified());
                continue;
            }
            sb.append(charSequence.charAt(i));
        }
        return sb.toString();
    }


    /**
     * 这是覆盖某些字符的方法。比如这句“你是个傻瓜，你确实是个傻蛋”返回给客户端的话
     * 但是，里面包含“傻瓜”，“傻蛋”这两个但有侮辱性的词，后台希望过滤掉这两个词
     *
     * @param charSequence 字符文本
     * @param flag         以什么标志来掩盖，比如 用星号 * 来掩盖
     * @param format       掩盖哪些数据
     * @return 返回掩盖后的数据
     */
    public String repalceSequence(String charSequence, CharReplacedEnum flag, String format) throws InformationException {
        flag = getFlag(charSequence, flag);
        if (isEmpty(format)) {
            return charSequence;
        }
        char[] charSequences = charSequence.toCharArray();
        for (int i = 0; i < charSequences.length; i++) {
            for (int j = 0; j < format.length(); j++) {
                if (charSequences[i] == format.charAt(j)) {
                    charSequences[i] = flag.getIdentified();
                    break;
                }
            }
        }
        return new String(charSequences);
    }

    /**
     * 验证{@code charSequence}不能为空，如果为空，则抛出异常。如果{@code flag}
     * 为空，则让@code flag}以星号来代替
     *
     * @param charSequence 文本内容
     * @param flag         替代标志
     * @return 返回flag对象
     */
    private CharReplacedEnum getFlag(String charSequence, CharReplacedEnum flag) throws InformationException {
        if (isEmpty(charSequence)) {
            throw new InformationException("文本内容不能为空");
        }
        flag = isNull(flag) ? REPLACED_STAR : flag;
        return flag;
    }

    /**
     * 验证文本内容的开始的下标位置和结束的下标位置
     *
     * @param charSequence 文本内容
     * @param start        文本内容的开始位置
     * @param end          文本内容的结束位置，但不包含该位置
     * @throws InformationException 不符合格式抛出异常
     */
    private void validStartEnd(String charSequence, int start, int end) throws InformationException {
        if (start < BigDecimal.ROUND_UP || end <= BigDecimal.ROUND_UP || start >= end) {
            throw new InformationException("开始位置不能小于0，结束位置不能小于等于0，开始位置不能大于结束位置");
        }
        if (start >= charSequence.length()) {
            throw new InformationException("开始位置不能大于文本的下标长度");
        }
    }

    /**
     * 这是格式化{@code format}的方法。如果{@code format}为空或全部为空字符，则用*填充，
     * 长度为{@code charSequence}的长度。如果不为空，则返回需要的format
     *
     * @param charSequence 源字符串
     * @param format       格式化字符串
     * @return 返回程序需要的格式化的字符串
     * @throws InformationException
     */
    private String getFormat(String charSequence, String format) throws InformationException {
        StringBuilder sb = new StringBuilder();
        if (isNotEmpty(format) && format.length() < charSequence.length()) {
            throw new InformationException("格式化字符串的长度不能小于字符串，比如charSequence=12345678901，format=*** **** ****");
        }
        if (isEmpty(format)) {
            for (int i = 0; i < charSequence.length(); i++) {
                sb.append(REPLACED_STAR.getIdentified());
            }
            return sb.toString();
        }
        return format;
    }

    /**
     * 这是被替代的枚举类，里面有些特殊字符，比如星号
     *
     * @author baoya
     * @version 1.0
     * @see NumberFilters#repalceSequence(String, CharReplacedEnum, String)
     * @since 1.1.0
     */
    public enum CharReplacedEnum {

        REPLACED_STAR('*'),

        REPLACED_AND('&'),

        REPLACED_HASH_SYMBOL('#'),

        REPLACED_QUESTION_MASK('?');


        private char identified;

        public void setIdentified(char identified) {
            this.identified = identified;
        }

        public char getIdentified() {
            return identified;
        }

        CharReplacedEnum(char identified) {
            this.identified = identified;
        }
    }
}
